﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Services;
using NGinnBPM.Runtime;
using System.IO;
using Newtonsoft.Json;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Services;
using NLog;
using Newtonsoft.Json.Linq;
using NGinnBPM.MessageBus;

namespace NGinnBPM.Services.Persistence
{
    /// <summary>
    /// Serializes task instances as json
    /// </summary>
    public class TaskInstanceJsonSerializer : ITaskInstanceSerializer
    {
        public ITaskInstanceFactory TaskInstanceFactory { get; set; }
        public IProcessPackageRepository PackageRepository { get; set; }
        public IServiceResolver ServiceLocator { get; set; }

        private Logger log = LogManager.GetCurrentClassLogger();

        
        private JsonSerializer GetSerializer()
        {
            JsonSerializer ser = new JsonSerializer();
            ser.Converters.Add(new Newtonsoft.Json.Converters.IsoDateTimeConverter());
            ser.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
            ser.NullValueHandling = NullValueHandling.Ignore;
            ser.TypeNameHandling = TypeNameHandling.None;
            ser.MissingMemberHandling = MissingMemberHandling.Error;
            ser.ObjectCreationHandling = ObjectCreationHandling.Replace;
            
            
            if (ServiceLocator != null)
            {
                foreach (JsonConverter conv in ServiceLocator.GetAllInstances<JsonConverter>())
                    ser.Converters.Add(conv);
            }

            return ser;
        }
        
        public string SerializeTaskInstance(TaskInstance ti)
        {
            StringWriter sw = new StringWriter();
            SerializeTaskInstance(ti, sw);
            return sw.ToString();
        }

      
        #region ITaskInstanceSerializer Members

        public void SerializeTaskInstance(TaskInstance ti, TextWriter tw)
        {
            JsonSerializer ser = GetSerializer();
            JObject job = JObject.FromObject(ti, ser);
            if (ti is MultiTaskInstance)
                job.Add("__NG_multiInstance", new JValue(true));
            job.Add("__NG_taskType", new JValue(ti.GetType().Name));
            JsonTextWriter jtw = new JsonTextWriter(tw);
            jtw.Formatting = Formatting.Indented;
            ser.Serialize(jtw, job);
            jtw.Flush();
        }

        public TaskInstance DeserializeTaskInstance(TextReader tr, Type taskType)
        {
            JsonTextReader jtr = new JsonTextReader(tr);

            JsonSerializer ser = GetSerializer();
            JObject job = (JObject)ser.Deserialize(jtr);
            string pdi = job.Value<string>("ProcessDefinitionId");
            string tid = job.Value<string>("TaskId");
            job.Remove("__NG_taskType");
            ProcessDefinition pd = PackageRepository.GetProcess(pdi);
            Task tsk = pd.GetTask(tid);

            TaskInstance ti = null;
            if (tsk == pd.Body)
            {
                ti = new ProcessInstance();
            }
            else if (tsk is CompositeTask)
            {
                ti = new CompositeTaskInstance();
            }
            else
            {
                if (tsk.IsMultiInstance)
                {
                    JToken tok;
                    if (job.TryGetValue("__NG_multiInstance", out tok) && tok is JValue && Convert.ToBoolean(((JValue)tok).Value))
                    {
                        ti = new MultiTaskInstance();
                        job.Remove("__NG_multiInstance");
                    }
                    else
                        ti = TaskInstanceFactory.CreateTaskInstance(tsk);
                }
                else
                    ti = TaskInstanceFactory.CreateTaskInstance(tsk);
            }
            ti.ParentProcess = pd;
            
            ser.Populate(job.CreateReader(), ti);
            return ti;
        }
        

        /// <summary>
        /// Return task instance state as a JObject
        /// </summary>
        /// <param name="ti"></param>
        /// <returns></returns>
        public JObject GetTaskInstanceState(TaskInstance ti)
        {
            return JObject.FromObject(ti, GetSerializer());
        }

        #endregion
    }
}
